package org.jeecg.feishu.api.service.impl;

import java.util.List;
import java.util.concurrent.TimeUnit;

import org.jeecg.feishu.api.config.FeishuConfig;
import org.jeecg.feishu.api.constant.FeishuApiConstants;
import org.jeecg.feishu.api.dto.AppAccessToken;
import org.jeecg.feishu.api.dto.FeishuPageResponse;
import org.jeecg.feishu.api.dto.FeishuRequest;
import org.jeecg.feishu.api.dto.FeishuResponse;
import org.jeecg.feishu.api.enums.AccessTokenType;
import org.jeecg.feishu.api.service.FeishuApiService;
import org.jeecg.feishu.common.exception.FeishuException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class FeishuApiServiceImpl implements FeishuApiService {

    /**
     * 返回code校验, 0:成功, 40013：未查询到部门数据, 40007：未查询到用户数据
     */
    private final List<Integer> excludeReultCode = ImmutableList.of(0, 40013, 40007);

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public <I, O> O send(FeishuConfig config, FeishuRequest<I, O> request) throws FeishuException {
        request.init();

        ResponseEntity<String> responseEntity = null;

        // 失败重试次数
        int i = 0;

        do {

            AccessTokenType accessTokenType = request.getAccessTokenType();
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json; charset=utf-8");
            headers.setContentType(MediaType.APPLICATION_JSON);
            if (!AccessTokenType.None.equals(accessTokenType)) {
                String token = accessToken(config, accessTokenType, false);
                // 鉴权
                headers.add("Authorization", "Bearer " + token);
            }

            // 设置header和body
            HttpEntity httpEntity = new HttpEntity(headers);
            if (!StringUtils.isEmpty(request.getInput())) {
                httpEntity = new HttpEntity(JSONObject.toJSONString(request.getInput()), headers);
            }

            String url = null;
            if (request.isOpenApi()) {
                url = request.fullUrl(config.getDomain(), config.getApiRootPath());
            } else {
                url = request.fullUrl(request.getDomain(), request.getUrlPrefix());
            }

            responseEntity = restTemplate.exchange(url, HttpMethod.resolve(request.getHttpMethod()), httpEntity, String.class);

            log.info("飞书API请求： {} : {}\n\theaders：{} \n\tbody: {}, \n\t response : {}", request.fullUrl(config.getDomain(), config.getApiRootPath()), HttpMethod.resolve(request.getHttpMethod()), httpEntity.getHeaders(), JSONObject.toJSONString(request.getInput()), responseEntity.getBody());

            try {
                validateResponse(responseEntity);
            } catch (FeishuException e) {
                request.setRetry(true);
                // tenant_access_token not valid, refresh token
                if (FeishuApiConstants.TOKEN_NOT_VALID_CODE == e.getCode()) {
                    accessToken(config, accessTokenType, true);
                }
                if (i >= 2) {
                    throw e;
                }
            }
        } while (request.isRetry() && i++ < 2);

        if (responseEntity == null) {
            throw new FeishuException(500, "飞书请求结果异常");
        }

        String body = responseEntity.getBody();
        FeishuResponse response = JSONObject.parseObject(body, FeishuResponse.class);
        O output = request.getOutput();
        if (request.isNotDataField()) {
            request.setOutput((O) JSONObject.parseObject(body, output.getClass()));
        } else {
            request.setOutput((O) JSONObject.parseObject(JSONObject.toJSONString(response.getData()), output.getClass()));
        }
        request.setResponse(response);
        return request.getOutput();
    }

    @Override
    public String accessToken(FeishuConfig config, AccessTokenType accessTokenType, boolean refresh) {
        String token = null;
        String url = null;
        String cacheKey = null;
        switch (accessTokenType) {
            case App:
                token = (String) config.getStore().get(FeishuConfig.APP_TOKEN_KEY + config.getAppId());
                cacheKey = FeishuConfig.APP_TOKEN_KEY + config.getAppId();
                url = FeishuApiConstants.APP_ACCESS_TOKEN_INTERNAL_URL_PATH;
                break;
            case Tenant:
            default:
                token = (String) config.getStore().get(FeishuConfig.TENANT_TOKEN_KEY + config.getAppId());
                cacheKey = FeishuConfig.TENANT_TOKEN_KEY + config.getAppId();
                url = FeishuApiConstants.TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH;
                break;
        }
        if (!StringUtils.isEmpty(token) && !refresh) {
            return token;
        }
        FeishuRequest<FeishuConfig, AppAccessToken> request = FeishuRequest.newRequestByAuth(url, "POST", config, new AppAccessToken());
        AppAccessToken appAccessToken = send(config, request);
        config.getStore().put(cacheKey + config.getAppId(), accessTokenType.getTokenFn().apply(appAccessToken), appAccessToken.getExpire(), TimeUnit.SECONDS);
        return appAccessToken.getTenantAccessToken();
    }

    /**
     * 校验api返回结果
     *
     * @param responseEntity
     */
    private void validateResponse(ResponseEntity<String> responseEntity) {
        int statusCode = responseEntity.getStatusCodeValue();
        if (statusCode >= 300 && statusCode < 400) {
            throw new FeishuException(300, String.format("api访问重定向: HTTPStatusCode = %s, response = %s", responseEntity.getStatusCode().value(), responseEntity.getBody()));
        }
        FeishuResponse result = null;
        try {
            result = JSONObject.parseObject(responseEntity.getBody(), FeishuResponse.class);
        } catch (Exception e) {
            log.error("飞书请json序列化失败", e);
        }
        if (result == null) {
            throw new FeishuException(500, "api访问失败: 返回结果为空，HTTPStatusCode = " + responseEntity.getStatusCode().value());
        }

        if (!excludeReultCode.contains(result.getCode())) {
            throw new FeishuException(result.getCode(), String.format("飞书API服务访问失败：msg = %s", result.toString()));
        }
    }

//    @Override
//    public <I, O> List<O> pageQuery(FeishuConfig config, FeishuRequest<I, FeishuPageResponse<O>> request) {
//        List<O> list = Lists.newArrayList();
//        FeishuPageResponse response = null;
//        do {
//            response = send(config, request);
//            if (response == null || CollectionUtils.isEmpty(response.getItems())) {
//                break;
//            }
//            list.addAll(JSONArray.parseArray(JSONArray.toJSONString(response.getItems(), )));
//            String pageToken = response.getPageToken();
//            request.addOptFn(opt -> opt.getQueryParams().put("page_token", pageToken));
//        } while (response.getHasMore() != null && response.getHasMore());
//        return list;
//    }

    @Override
    public <I, O> List<O> pageQuery(FeishuConfig config, FeishuRequest<I, O> request) {
        List<O> list = Lists.newArrayList();
        FeishuPageResponse response = null;
        FeishuRequest request1 = request.copy();
        request1.setOutput(new FeishuPageResponse());
        do {
            response = (FeishuPageResponse) send(config, request1);
            if (response == null || CollectionUtils.isEmpty(response.getItems())) {
                break;
            }
            List<O> objects = (List<O>) JSONArray.parseArray(JSONArray.toJSONString(response.getItems()), request.getOutput().getClass());
            list.addAll(objects);
            String pageToken = response.getPageToken();
            request.addOptFn(opt -> opt.getQueryParams().put("page_token", pageToken));
        } while (response.getHasMore() != null && response.getHasMore());
        return list;
    }
}
